//! Skill handler abstraction for agent capabilities.
//!
//! This module provides the core skill handler trait and related types for implementing
//! agent capabilities. Skills are the building blocks of agent functionality, handling
//! requests, managing state, and producing results.
//!
//! # Overview
//!
//! - [`SkillHandler`]: Core trait for implementing skill logic
//! - [`OnRequestResult`]: Result returned when a skill handles a request
//! - [`OnInputResult`]: Result returned when a skill receives user input
//! - [`SkillSlot`]: Type-erased slot for requesting additional input
//! - [`Artifact`]: Output artifacts produced by skill execution
//!
//! # Examples
//!
//! ```ignore
//! use radkit::agent::{SkillHandler, OnRequestResult, Artifact};
//! use anyhow::Result;
//!
//! struct WeatherSkill;
//!
//! #[async_trait::async_trait]
//! impl SkillHandler for WeatherSkill {
//!     async fn on_request(
//!         &self,
//!         task_context: &mut TaskContext,
//!         context: &Context,
//!         runtime: &dyn Runtime,
//!         content: Content,
//!     ) -> Result<OnRequestResult> {
//!         let artifact = Artifact::from_json("forecast.json", &weather_data)?;
//!         Ok(OnRequestResult::Completed {
//!             message: Some(Content::from_text("Weather retrieved")),
//!             artifacts: vec![artifact],
//!         })
//!     }
//! }
//! ```

use crate::compat::{MaybeSend, MaybeSync};
use crate::errors::AgentError;
use crate::models::Content;
use crate::runtime::context::{Context, TaskContext};
use crate::runtime::Runtime;
use base64::Engine;
use serde::{Deserialize, Serialize};

/// Artifact produced by a skill execution.
///
/// Represents a tangible output generated by an agent, containing one or more parts.
/// Framework automatically converts to A2A Artifact with proper Part types.
///
/// # Examples
///
/// ```ignore
/// // Create JSON artifact
/// let artifact = Artifact::from_json("result.json", &data)?;
///
/// // Create text artifact
/// let artifact = Artifact::from_text("output.txt", "Hello, world!");
///
/// // Create file artifact
/// let artifact = Artifact::from_file("image.png", "image/png", image_bytes);
/// ```
#[derive(Debug, Clone)]
pub struct Artifact {
    /// Artifact identifier/name
    name: String,
    /// Artifact content
    content: Content,
}

impl Artifact {
    /// Create an artifact from JSON data.
    ///
    /// Automatically converts to A2A Artifact with `DataPart`.
    ///
    /// # Arguments
    ///
    /// * `name` - Artifact name/identifier
    /// * `data` - Serializable data
    ///
    /// # Errors
    ///
    /// Returns error if data cannot be serialized.
    pub fn from_json<T: Serialize>(name: &str, data: &T) -> Result<Self, AgentError> {
        use crate::models::content_part::{ContentPart, Data, DataSource};

        let json_str = serde_json::to_string(data)?;

        // Encode JSON as base64 for A2A protocol
        let base64_data = base64::engine::general_purpose::STANDARD.encode(json_str.as_bytes());

        let data_part = Data {
            content_type: "application/json".to_string(),
            source: DataSource::Base64(base64_data),
            name: Some(name.to_string()),
        };

        Ok(Self {
            name: name.to_string(),
            content: Content::from(ContentPart::Data(data_part)),
        })
    }

    /// Create an artifact from text content.
    ///
    /// Automatically converts to A2A Artifact with `TextPart`.
    ///
    /// # Arguments
    ///
    /// * `name` - Artifact name/identifier
    /// * `text` - Text content
    #[must_use]
    pub fn from_text(name: &str, text: &str) -> Self {
        Self {
            name: name.to_string(),
            content: Content::from_text(text),
        }
    }

    /// Create an artifact from file data.
    ///
    /// Automatically converts to A2A Artifact with `FilePart`.
    ///
    /// # Arguments
    ///
    /// * `name` - Artifact name/identifier
    /// * `mime_type` - MIME type of the file
    /// * `data` - File bytes
    #[must_use]
    pub fn from_file(name: &str, mime_type: &str, data: Vec<u8>) -> Self {
        use crate::models::content_part::{ContentPart, Data, DataSource};

        // Encode file bytes as base64 for A2A protocol
        let base64_data = base64::engine::general_purpose::STANDARD.encode(&data);

        let data_part = Data {
            content_type: mime_type.to_string(),
            source: DataSource::Base64(base64_data),
            name: Some(name.to_string()),
        };

        Self {
            name: name.to_string(),
            content: Content::from(ContentPart::Data(data_part)),
        }
    }

    /// Get the artifact name.
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the artifact content.
    #[must_use]
    pub const fn content(&self) -> &Content {
        &self.content
    }
}

/// Wrapper for skill input slot values.
///
/// Used to track different input-required states in multi-turn conversations.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SkillSlot {
    value: serde_json::Value,
}

impl SkillSlot {
    /// Create a new skill slot from a serializable value.
    ///
    /// # Arguments
    ///
    /// * `value` - Slot value (typically an enum variant)
    pub fn new<T: Serialize>(value: T) -> Self {
        let serialized = serde_json::to_value(value).expect("Failed to serialize skill slot value");
        Self { value: serialized }
    }

    /// Deserialize the slot value into the requested type.
    pub fn deserialize<T>(&self) -> Result<T, AgentError>
    where
        T: serde::de::DeserializeOwned,
    {
        serde_json::from_value(self.value.clone()).map_err(|e| AgentError::SkillSlot(e.to_string()))
    }

    #[cfg(feature = "runtime")]
    pub(crate) fn into_value(self) -> serde_json::Value {
        self.value
    }

    pub(crate) const fn from_value_unchecked(value: serde_json::Value) -> Self {
        Self { value }
    }
}

/// Metadata describing a skill's public surface.
#[derive(Debug, Clone, Serialize)]
pub struct SkillMetadata {
    pub id: &'static str,
    pub name: &'static str,
    pub description: &'static str,
    #[serde(default)]
    pub tags: &'static [&'static str],
    #[serde(default)]
    pub examples: &'static [&'static str],
    #[serde(default)]
    pub input_modes: &'static [&'static str],
    #[serde(default)]
    pub output_modes: &'static [&'static str],
}

impl SkillMetadata {
    #[must_use]
    pub const fn new(
        id: &'static str,
        name: &'static str,
        description: &'static str,
        tags: &'static [&'static str],
        examples: &'static [&'static str],
        input_modes: &'static [&'static str],
        output_modes: &'static [&'static str],
    ) -> Self {
        Self {
            id,
            name,
            description,
            tags,
            examples,
            input_modes,
            output_modes,
        }
    }
}

/// Trait implemented by skills that expose metadata to the agent builder.
pub trait RegisteredSkill: SkillHandler + Sized {
    fn metadata() -> &'static SkillMetadata;
}

/// Result of skill handler's `on_request` method.
///
/// Controls the A2A task lifecycle. Maps directly to A2A task states.
#[derive(Debug)]
pub enum OnRequestResult {
    /// Task needs user input.
    ///
    /// Maps to A2A `TaskState::InputRequired` (terminal, final=true).
    InputRequired {
        /// Message to send to user
        message: Content,
        /// Slot to track which input is being requested
        slot: SkillSlot,
    },

    /// Task completed successfully.
    ///
    /// Maps to A2A `TaskState::Completed` (terminal, final=true).
    /// Artifacts here are marked as final.
    Completed {
        /// Optional completion message
        message: Option<Content>,
        /// Final artifacts produced
        artifacts: Vec<Artifact>,
    },

    /// Task failed due to an error.
    ///
    /// Maps to A2A `TaskState::Failed` (terminal, final=true).
    Failed {
        /// Error message
        error: Content,
    },

    /// Task was rejected and not started.
    ///
    /// Maps to A2A `TaskState::Rejected` (terminal, final=true).
    Rejected {
        /// Rejection reason
        reason: Content,
    },
}

/// Result of skill handler's `on_input_received` method.
///
/// Controls continuation after receiving user input.
#[derive(Debug)]
pub enum OnInputResult {
    /// Still need more input.
    ///
    /// Can ask for input again if the user's response was invalid.
    InputRequired {
        /// Message to send to user
        message: Content,
        /// New slot for the next input
        slot: SkillSlot,
    },

    /// Task completed after receiving input.
    ///
    /// Maps to A2A `TaskState::Completed` (terminal, final=true).
    Completed {
        /// Optional completion message
        message: Option<Content>,
        /// Final artifacts produced
        artifacts: Vec<Artifact>,
    },

    /// Failed to process the input.
    ///
    /// Maps to A2A `TaskState::Failed` (terminal, final=true).
    Failed {
        /// Error message
        error: Content,
    },
}

/// Trait for implementing skill handlers.
///
/// Skills are the building blocks of A2A agents. Each skill handles a specific capability
/// and is annotated with the `#[skill]` macro for A2A metadata.
///
/// # Examples
///
/// ```ignore
/// #[skill(
///     id = "my_skill",
///     name = "My Skill",
///     description = "Does something useful",
///     tags = ["utility"],
///     examples = ["Do something"],
///     input_modes = ["text/plain"],
///     output_modes = ["application/json"]
/// )]
/// pub struct MySkill;
///
/// #[async_trait]
/// impl SkillHandler for MySkill {
///     async fn on_request(
///         &self,
///         task_context: &mut TaskContext,
///         context: &Context,
///         runtime: &dyn Runtime,
///         content: Content,
///     ) -> Result<OnRequestResult> {
///         // Implementation here
///         Ok(OnRequestResult::Completed {
///             message: Some(Content::from_text("Done")),
///             artifacts: vec![],
///         })
///     }
/// }
/// ```
#[cfg_attr(
    all(target_os = "wasi", target_env = "p1"),
    async_trait::async_trait(?Send)
)]
#[cfg_attr(
    not(all(target_os = "wasi", target_env = "p1")),
    async_trait::async_trait
)]
pub trait SkillHandler: MaybeSend + MaybeSync {
    /// Handle initial skill invocation.
    ///
    /// This is the primary entry point for a new task. Contains the "happy path" logic.
    /// If it succeeds, return `Completed`. If it needs more information, save partial
    /// work and return `InputRequired`.
    ///
    /// # Arguments
    ///
    /// * `task_context` - Mutable context for persisting state
    /// * `context` - Immutable execution context
    /// * `runtime` - Runtime services
    /// * `content` - Input content from the user
    ///
    /// # Errors
    ///
    /// Returns error if skill execution fails in an unexpected way.
    async fn on_request(
        &self,
        task_context: &mut TaskContext,
        context: &Context,
        runtime: &dyn Runtime,
        content: Content,
    ) -> Result<OnRequestResult, AgentError>;

    /// Handle continued interaction after input request.
    ///
    /// Only called if `on_request` or a previous `on_input_received` returned `InputRequired`.
    /// Load partial state, process the new input, and continue the logic.
    ///
    /// Default implementation rejects all input.
    ///
    /// # Arguments
    ///
    /// * `task_context` - Mutable context with saved state
    /// * `context` - Immutable execution context
    /// * `runtime` - Runtime services
    /// * `content` - New input content from the user
    ///
    /// # Errors
    ///
    /// Returns error if input processing fails in an unexpected way.
    async fn on_input_received(
        &self,
        _task_context: &mut TaskContext,
        _context: &Context,
        _runtime: &dyn Runtime,
        _content: Content,
    ) -> Result<OnInputResult, AgentError> {
        Ok(OnInputResult::Failed {
            error: "This skill does not support multi-turn input".into(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::content_part::{ContentPart, DataSource};
    use serde::{Deserialize, Serialize};

    #[test]
    fn artifact_from_json_encodes_payload() {
        let payload = serde_json::json!({ "value": 42 });
        let artifact = Artifact::from_json("data.json", &payload).expect("artifact");

        assert_eq!(artifact.name(), "data.json");
        let content = artifact.content();
        let parts = content.parts();
        assert_eq!(parts.len(), 1);

        let data_part = match &parts[0] {
            ContentPart::Data(data) => data,
            other => panic!("expected data part, found {other:?}"),
        };

        assert_eq!(data_part.content_type, "application/json");
        assert_eq!(data_part.name.as_deref(), Some("data.json"));

        let encoded = match &data_part.source {
            DataSource::Base64(b64) => b64,
            other => panic!("expected base64 data, found {other:?}"),
        };

        let decoded = base64::engine::general_purpose::STANDARD
            .decode(encoded)
            .expect("base64 decoding");
        let text = String::from_utf8(decoded).expect("utf8");
        assert_eq!(text, payload.to_string());
    }

    #[test]
    fn artifact_from_file_wraps_bytes() {
        let artifact = Artifact::from_file("image.png", "image/png", vec![1, 2, 3, 4]);
        assert_eq!(artifact.name(), "image.png");

        let data_part = match &artifact.content().parts()[0] {
            ContentPart::Data(data) => data,
            other => panic!("expected data part, found {other:?}"),
        };

        assert_eq!(data_part.content_type, "image/png");
        let encoded = match &data_part.source {
            DataSource::Base64(b64) => b64,
            other => panic!("expected base64 data, found {other:?}"),
        };

        let decoded = base64::engine::general_purpose::STANDARD
            .decode(encoded)
            .expect("base64 decoding");
        assert_eq!(decoded, vec![1, 2, 3, 4]);
    }

    #[test]
    fn skill_slot_round_trips() {
        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
        enum SlotState {
            AwaitingEmail,
            Confirm(String),
        }

        let slot = SkillSlot::new(SlotState::Confirm("example".into()));
        let decoded: SlotState = slot
            .deserialize()
            .expect("slot should deserialize successfully");
        assert_eq!(decoded, SlotState::Confirm("example".into()));
    }
}
